﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MIO.Client.Protocal;
using System.Net.Sockets;
using MIO.Client.Util;
using MIO.Client.Config;
using System.Net;
using System.Threading;
using MIO.Client.Daemon;
using MIO.Client.Ack;
using MIO.Client.Event;
using MIO.Client.RTT;

namespace MIO.Client
{
    public class IOClient
    {
        public const string TAG = nameof(IOClient);
        private Socket m_socket;
        private IPEndPoint m_serverIPE;
        private IPEndPoint m_localIPE;
        private volatile bool connected = false;
        private bool isFirst = true;
        private bool canReconnect = false;

        public event EventHandler Disconnect_Event = null;
        public event EventHandler ConnectSuccess_Event = null;
        public event EventHandler ReconnectSuccess_Event = null;

        private IOClientConfig config;
        private PacketHandler packetHandler;
        private KeepAliveDaemon keepAliveDaemon;
        private AckSendTask ackSendTask;
        private AckReceiveTask ackReceiveTask;
        private EventManager eventManager;

        private readonly object sendReceiveLock = new object();
        private readonly AutoResetEvent sendReceiveAutoEvent = new AutoResetEvent(false);

        private readonly IList<Packet> sendPacketList = new List<Packet>();
        private Thread sendTask = null;

        private RTTInfo rttInfo = null;
        private RTTHandler rttHandler = null;

        public IOClientConfig Config
        {
            get
            {
                return config;
            }
            private set
            {
                config = value;
            }
        }

        public PacketHandler PacketHandler
        {
            get
            {
                return packetHandler;
            }

            private set
            {
                packetHandler = value;
            }
        }

        public Socket UdpSocket
        {
            get
            {
                return m_socket;
            }
        }

        public KeepAliveDaemon KeepAliveDaemon
        {
            get
            {
                return keepAliveDaemon;
            }

            private set
            {
                keepAliveDaemon = value;
            }
        }



        public bool Connected
        {
            get
            {
                return connected;
            }

            private set
            {
                if (connected != value)
                {
                    connected = value;

                    if (value)
                    {

                        if (isFirst)
                        {
                            if (Config?.Debug == true)
                            {
                                Log.d(TAG, "Successfually connect");
                            }
                            Task.Factory.StartNew(() => { ConnectSuccess_Event?.Invoke(this, EventArgs.Empty); });
                        }
                        else
                        {
                            if (Config?.Debug == true)
                            {
                                Log.d(TAG, "Successfually reconnect");
                            }
                            Task.Factory.StartNew(() => { ReconnectSuccess_Event?.Invoke(this, EventArgs.Empty); });
                        }
                        if (isFirst)
                        {
                            isFirst = false;
                        }

                    }
                    else
                    {
                        if (Config?.Debug == true)
                        {
                            Log.d(TAG, "Disconnect");
                        }
                        Task.Factory.StartNew(() =>
                        {
                            Disconnect_Event?.Invoke(this, EventArgs.Empty);
                        });
                    }
                }
            }
        }

        public EventManager EventManager
        {
            get
            {
                return eventManager;
            }

            private set
            {
                eventManager = value;
            }
        }

        internal AckSendTask AckSendTask
        {
            get
            {
                return ackSendTask;
            }

            private set
            {
                ackSendTask = value;
            }
        }

        public AckReceiveTask AckReceiveTask
        {
            get
            {
                return ackReceiveTask;
            }

            private set
            {
                ackReceiveTask = value;
            }
        }

        public RTTHandler RttHandler
        {
            get
            {
                return rttHandler;
            }

            private set
            {
                rttHandler = value;
            }
        }



        public IOClient(IOClientConfig config)
        {
            Config = config;
            PacketHandler = new PacketHandler(this);

            IPAddress iPAddress = IPAddress.Parse(Config.ServerIp);
            m_serverIPE = new IPEndPoint(iPAddress, Config.ServerPort);
            m_localIPE = new IPEndPoint(IPAddress.Any, Config.LocalPort);

            KeepAliveDaemon = new KeepAliveDaemon(this);
            KeepAliveDaemon.LostKeepAlive_Event += (sender, e) =>
            {
                Connected = false;
                reconnect();
            };
            AckSendTask = new AckSendTask(this);
            AckReceiveTask = new AckReceiveTask(this);

            EventManager = new EventManager(this);


        }

        public void connect()
        {
            if (m_socket != null)
                innerClose();
            canReconnect = true;
            m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            m_socket.SendBufferSize = 2048;
            m_socket.ReceiveBufferSize = 2048;
            m_socket.Bind(m_localIPE);
            SocketAsyncEventArgs receiveSAEA = new SocketAsyncEventArgs();
            byte[] receiveBuffer = new byte[548];
            receiveSAEA.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
            receiveSAEA.Completed += OnReceiveCompleted;
            receiveSAEA.RemoteEndPoint = m_serverIPE;
            m_socket.ReceiveAsync(receiveSAEA);

            m_socket.Connect(m_serverIPE);

            KeepAliveDaemon.start(false);
            AckSendTask.startup(true);
            AckReceiveTask.startup(true);

            RttHandler = new RTTHandler(Config.AckTimeout);

            if (sendTask != null)
                sendTask.Abort();
            sendTask = new Thread(getAndSend);
            sendTask.IsBackground = true;
            sendTask.Start();
        }


        private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.OperationAborted)
                return;

            Socket socket = sender as Socket;

            if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
            {
                Connected = true;
                int lengthBuffer = e.BytesTransferred;
                byte[] receiveBuffer = e.Buffer;
                byte[] buffer = new byte[lengthBuffer];
                Buffer.BlockCopy(receiveBuffer, 0, buffer, 0, lengthBuffer);

                PacketHandler.messageReceived(buffer, buffer.Length);
                try
                {
                    socket.ReceiveAsync(e);
                }
                catch { }
            }
            else if (e.SocketError == SocketError.ConnectionReset && e.BytesTransferred == 0)
            {
                Connected = false;
                reconnect();
            }
            else
            {
                return;
            }
        }

        private void reconnect()
        {
            if (!canReconnect)
                return;
            if (Config.AutoReConnect)
            {
                if (Config.Debug)
                {
                    Log.d(TAG, "Reconnecting..............");
                }
                Thread.Sleep(1);
                connect();
            }
        }

        public bool send(Packet packet)
        {
            Task.Factory.StartNew(() =>
            {
                if (packet.Type == PacketType.EVENT_MESSAGE)
                {
                    lock (sendPacketList)
                    {
                        sendPacketList.Add(packet);
                    }

                }
                else
                {
                    lock (sendPacketList)
                    {
                        sendPacketList.Insert(0, packet);
                    }
                }
            });
            return true;
        }


        private bool doSendTask(Packet packet)
        {
            if (m_socket != null && packet != null && (packet.Type != PacketType.EVENT_MESSAGE || Connected))
            {
                try
                {
                    if (packet.Type == PacketType.KEEP_ALIVE)
                    {
                        if (Config?.Debug == true)
                        {
                            Log.d(TAG,DateTime.Now + "Send KeepAlive Packet");
                        }
                    }
                    if (m_socket.Send(packet.toBytes()) > 0)
                    {
                        if (packet.Ack && !string.IsNullOrEmpty(packet.AckId) && packet.Type == PacketType.EVENT_MESSAGE)
                        {
                            if (!AckSendTask.exist(packet.AckId))
                            {
                                AckSendTask.put(packet);
                            }
                        }
                        return true;
                    }
                }
                catch (Exception ex)
                {
                    Connected = false;
                    Log.e(TAG, ex.Message, ex);
                }
            }
            return false;
        }

        private void getAndSend()
        {
            while (true)
            {
                Packet packet = null;
                lock (sendPacketList)
                {
                    if (sendPacketList.Count > 0)
                    {
                        packet = sendPacketList[0];
                        sendPacketList.Remove(packet);
                    }
                }
                if (packet != null)
                {
                    doSendTask(packet);
                }
            }
        }


        public bool sendToClient(string sessionId, string eventName, Object data, bool ack)
        {

            Packet p = new Packet()
            {
                Type = PacketType.EVENT_MESSAGE,
                SubType = eventName,
                SessionId = sessionId,
                DataContent = JsonUtil.object2JsonString(data),
                Ack = ack,
            };
            if (ack)
                p.AckId = CommonUtil.createUniqueId();
            return send(p);
        }

        public bool sendToClient(string sessionId, string eventName, Object data)
        {
            return sendToClient(sessionId, eventName, data, true);
        }

        public bool sendToServer(string eventName, Object data, bool ack)
        {
            return sendToClient("0", eventName, data, ack);
        }

        public bool sendToServer(string eventName, Object data)
        {
            return sendToClient("0", eventName, data, true);
        }

        public T sendToClientReceive<T>(string sessionId, string eventName, Object eventData, string receiveEventName, int sendTimeout = 5000)
        {

            Task<T> task = Task.Factory.StartNew<T>(() =>
            {
                lock (sendReceiveLock)
                {
                    T result = default(T);
                    EventManager.addEventDelegate<T>(receiveEventName, new EventPacketDelegate<T>((client, fromSessionId, data) =>
                    {
                        result = data;
                        sendReceiveAutoEvent.Set();
                    }));

                    sendToClient(sessionId, eventName, eventData);

                    sendReceiveAutoEvent.WaitOne(sendTimeout);

                    EventManager.removeEventDelegate(receiveEventName);

                    return result;
                }
            });
            return task.Result;
        }

        public T sendToServerReceive<T>(string eventName, Object eventData, string receiveEventName, int sendTimeout = 5000)
        {
            return sendToClientReceive<T>("0", eventName, eventData, receiveEventName, sendTimeout);
        }


        public void close()
        {
            sendToServer("exit", new VoidData());
            try
            {
                innerClose();
            }
            catch { }
            canReconnect = false;
        }

        public void innerClose()
        {
            if (m_socket != null)
            {
                try
                {
                    m_socket.Shutdown(SocketShutdown.Both);
                }
                finally
                {
                    m_socket.Close();
                    m_socket = null;
                }
            }

            if (KeepAliveDaemon != null)
                KeepAliveDaemon.stop();
            if (AckSendTask != null)
                AckSendTask.stop();
            if (AckReceiveTask != null)
                AckReceiveTask.stop();
            if (sendTask != null)
                sendTask.Abort();
            lock (sendPacketList)
            {
                sendPacketList.Clear();
            }
        }


        public void addLostPacketDelegate(LostPacketDelegate lostPacketDelegate)
        {
            this.EventManager?.addLostPacketDelegate(lostPacketDelegate);
        }

        public void addEventDelegate<T>(string eventName, EventPacketDelegate<T> eventPacketDelegate)
        {
            this.EventManager?.addEventDelegate<T>(eventName, eventPacketDelegate);
        }

    }
}
